package com.wujunshen.redis.wrapper;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.query.SortQueryBuilder;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Redis 集群实现了单机 Redis 中所有处理单个数据库键的命令。
 *
 * <p>Redis 集群不像单机 Redis 那样支持多数据库功能， 集群只使用默认的0号数据库， 并且不能使用 SELECT 命令<br>
 *
 * @author frank woo(吴峻申) <br>
 * email:<a href="mailto:frank_wjs@hotmail.com">frank_wjs@hotmail.com</a> <br>
 * @date 2020/2/7 5:33 下午 <br>
 */
@Slf4j
@NoArgsConstructor
@EnableAutoConfiguration
public class MyRedisTemplate {
    @Resource
    private RedisTemplate<Object, Object> redisTemplate;
    
    @Resource
    private JedisConnectionFactory connectionFactory;
    
    /**
     * 清空redis缓存数据（慎用）
     *
     * @return 结果
     */
    public String flushDB() {
        connectionFactory.getConnection().flushDb();
        return "ok";
    }
    
    /**
     * 目前集群模式无法支持管道模式，会报异常： Pipeline is currently not supported for JedisClusterConnection.
     *
     * <p>执行管道
     *
     * @param key 键值
     * @return
     */
    public List<Object> executePipelined(final String key) {
        final RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        final Long listSize = redisTemplate.opsForList().size(key);
        
        return redisTemplate.executePipelined(
                (RedisCallback<String>)
                        connection -> {
                            for (int i = 0; i < listSize; i++) {
                                byte[] listName = serializer.serialize(key);
                                connection.rPop(listName);
                            }
                            return null;
                        },
                serializer);
    }
    
    /**
     * 如果key值不存在，则存储
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public void setIfAbsent(String key, String value) {
        redisTemplate.opsForValue().setIfAbsent(key, value);
    }
    
    /**
     * 添加value值在原有value值后面
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public int append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }
    
    /**
     * 将键值改名
     *
     * @param oldKey 旧键值
     * @param newKey 新键值
     */
    public void rename(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }
    
    /**
     * 检查是否存在该key值
     *
     * @param key 键值
     * @return 是否存在
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }
    
    /**
     * 添加key和value
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 根据key值查询value
     *
     * @param key 键值
     * @return 键值对应值
     */
    public String getBy(String key) {
        return redisTemplate.opsForValue().get(key) != null
                ? String.valueOf(redisTemplate.opsForValue().get(key))
                : null;
    }
    
    /**
     * 添加key和value时候，设定失效时长
     *
     * @param key        键值
     * @param value      键值对应值
     * @param expireTime 过期时间
     */
    public void setExpire(String key, String value, Long expireTime) {
        redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
    }
    
    /**
     * 批量添加key和value
     *
     * @param map 键值和键值对应值的map集合
     */
    public void multiSet(Map<String, String> map) {
        redisTemplate.opsForValue().multiSet(map);
    }
    
    /**
     * 批量查询value
     *
     * @param keys 键值
     * @return 键值对应值
     */
    public List<Object> multiGet(Set<Object> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }
    
    /**
     * 批量删除key
     *
     * @param keys 键值
     */
    public void deleteMulti(Set<Object> keys) {
        redisTemplate.delete(keys);
    }
    
    /**
     * 删除key
     *
     * @param key 键值
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }
    
    /**
     * 获取value时候，更新新的value值
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public void getAndSet(String key, String value) {
        redisTemplate.opsForValue().getAndSet(key, value);
    }
    
    /**
     * 根据strIndex和endIndex,截取value值
     *
     * @param key      键值
     * @param strIndex 索引起始
     * @param endIndex -1为获取所有value值
     * @return 范围内的值
     */
    public String getRangeOf(String key, long strIndex, long endIndex) {
        return redisTemplate.opsForValue().get(key, strIndex, endIndex);
    }
    
    /**
     * 获取所有key
     *
     * @param patternKey 键值（通配符）
     * @return 所有key的Set集合
     */
    private Set<Object> getKeys(String patternKey) {
        return redisTemplate.keys(patternKey);
    }
    
    /**
     * 查询所有value
     *
     * @param patternKey 键值（通配符）
     * @return 所有查询所有value的List集合
     */
    public List<Object> getAllValuesBy(String patternKey) {
        Set<Object> keys = getKeys(patternKey);
        
        return redisTemplate.opsForValue().multiGet(keys);
    }
    
    /**
     * 写入list，并设置失效时长
     *
     * @param key   键值
     * @param value 命令实体类
     */
    public void addElementOfList(final String key, String value) {
        redisTemplate.opsForList().leftPush(key, value);
    }
    
    /**
     * 每次读取一条（出栈）
     *
     * @param key redis的键
     * @return redis被读取出的值对象
     */
    public Object getElementOfListBy(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }
    
    /**
     * 读取范围内的element值，endIndex若为-1，则为读取所有element
     *
     * @param key      键值
     * @param strIndex 索引起始值
     * @param endIndex 索引终止值
     * @return 范围内的element值的list集合
     */
    public List<Object> getListBy(String key, int strIndex, int endIndex) {
        return redisTemplate.opsForList().range(key, strIndex, endIndex);
    }
    
    /**
     * 根据键值获取命令队列
     *
     * @param key 键值
     * @return 命令队列
     */
    public Long getSizeOfList(String key) {
        return redisTemplate.opsForList().size(key);
    }
    
    /**
     * list/set排序
     *
     * @param key 键值
     * @return 排序后的结果集合
     */
    public List<Object> sortBy(String key) {
        SortQueryBuilder<Object> builder = SortQueryBuilder.sort(key);
        builder.alphabetical(true); // 对字符串使用“字典顺序”
        
        return redisTemplate.sort(builder.build());
    }
    
    /**
     * 外部设置sort条件，传入作为方法参数
     *
     * @param builder 排序查询构造器
     * @return 排序后的结果集合
     */
    public List<Object> sortBy(SortQueryBuilder<Object> builder) {
        return redisTemplate.sort(builder.build());
    }
    
    /**
     * 排序完，存储至新的集合中
     *
     * @param builder  排序查询构造器
     * @param storeKey 已排序的键值
     * @return 操作是否成功
     */
    public Long sortByAndStore(SortQueryBuilder<Object> builder, Object storeKey) {
        return redisTemplate.sort(builder.build(), storeKey);
    }
    
    /**
     * 实现分页排序
     *
     * @param key      键值
     * @param strIndex 索引起始值
     * @param endIndex 索引终止值
     * @return 排序后的结果集合
     */
    public List<Object> sortPageBy(String key, long strIndex, long endIndex) {
        SortQueryBuilder<Object> builder = SortQueryBuilder.sort(key);
        builder.alphabetical(true); // 对字符串使用“字典顺序”
        builder.limit(strIndex, endIndex);
        
        return redisTemplate.sort(builder.build());
    }
    
    /**
     * 降序排序
     *
     * @param key 键值
     * @return 排序后的结果集合
     */
    public List<Object> sortDescBy(String key) {
        SortQueryBuilder<Object> builder = SortQueryBuilder.sort(key);
        builder.alphabetical(true); // 对字符串使用“字典顺序”
        builder.order(SortParameters.Order.DESC);
        
        return redisTemplate.sort(builder.build());
    }
    
    /**
     * 根据strIndex和endIndex,截取list的element值
     *
     * @param key      键值
     * @param strIndex 索引起始值
     * @param endIndex -1为获取所有value值
     * @return 排序后的结果集合
     */
    public List<Object> getRangeOfList(String key, long strIndex, long endIndex) {
        return redisTemplate.opsForList().range(key, strIndex, endIndex);
    }
    
    /**
     * 根据键值,获取value的类型
     *
     * @param key 键值
     */
    public String getKindOfValue(String key) {
        return redisTemplate.type(key).code();
    }
    
    /**
     * 修改list中某个element的值
     *
     * @param key   键值
     * @param value 键值对应值
     * @param index 索引
     */
    public void setElementOfList(String key, String value, long index) {
        redisTemplate.opsForList().set(key, index, value);
    }
    
    /**
     * 获取list指定index的值
     *
     * @param key   键值
     * @param index 索引
     * @return 指定index的值
     */
    public String getElementOfList(String key, long index) {
        return String.valueOf(redisTemplate.opsForList().index(key, index));
    }
    
    /**
     * 删除list指定index的值
     *
     * @param key   键值
     * @param value 键值对应值
     * @param index 索引
     */
    public void removeElementOfList(String key, String value, long index) {
        redisTemplate.opsForList().remove(key, index, value);
    }
    
    /**
     * 删除区间以外的数据
     *
     * @param key      键值
     * @param strIndex 索引起始值
     * @param endIndex 索引终止值
     */
    public void removeElementOfListExcept(String key, long strIndex, long endIndex) {
        redisTemplate.opsForList().trim(key, strIndex, endIndex);
    }
    
    /**
     * 添加元素到sortedSet
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public void addElementOfZSet(String key, String value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }
    
    /**
     * 从sortedSet获取所有元素(按照score正序排序)
     *
     * @param key 键值
     * @return 排序后的结果集合
     */
    public Set<Object> getElementOfZSet(String key, long strIndex, long endIndex) {
        return redisTemplate.opsForZSet().range(key, strIndex, endIndex);
    }
    
    /**
     * 从sortedSet获取所有元素(按照score倒序排序)
     *
     * @param key 键值
     * @return 排序后的结果集合
     */
    public Set<Object> getElementOfZSetDesc(String key, long strIndex, long endIndex) {
        return redisTemplate.opsForZSet().reverseRange(key, strIndex, endIndex);
    }
    
    /**
     * 从sortedSet中移除某元素，成功返回被删除的元素个数，元素不存在或移除失败返回0L
     *
     * @param key   键值
     * @param value 键值对应值
     * @return 操作是否成功
     */
    public Long removeMemberOfZSet(String key, Object... value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }
    
    /**
     * 从sortedSet中移除某元素，成功返回被删除的元素个数，元素不存在或移除失败返回0L
     * endIndex如果大于sortedSet的size，则返回的是从strIndex开始实际被删除的元素个数
     * 如，size为4，strIndex为0，endIndex为5，则实际删除index为0,1,2,3的元素，返回4L
     *
     * @param key      键值
     * @param strIndex 索引起始值
     * @param endIndex 索引终止值
     * @return 操作是否成功
     */
    public Long removeMemberOfZSetRange(String key, long strIndex, long endIndex) {
        return redisTemplate.opsForZSet().removeRange(key, strIndex, endIndex);
    }
    
    /**
     * 从sortedSet中移除某元素，成功返回被删除的元素个数，元素不存在或移除失败返回0L minScore如果大于maxScore，返回0L
     * minScore和maxScore之间无sortedSet已存在的score，返回0L
     *
     * @param key      键值
     * @param minScore 最小分值
     * @param maxScore 最大分值
     * @return 操作是否成功
     */
    public Long removeMemberOfZSetRangeByScore(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
    }
    
    /**
     * 从sortedSet中获取score在minScore和maxScore之间的元素个数 minScore如果大于maxScore，返回0L
     * minScore和maxScore之间无sortedSet已存在的score，返回0L
     *
     * @param key      键值
     * @param minScore 最小分值
     * @param maxScore 最大分值
     * @return minScore和maxScore之间的元素个数
     */
    public Long count(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().count(key, minScore, maxScore);
    }
    
    /**
     * 获取sortedSet的元素个数
     *
     * @param key 键值
     * @return 元素个数
     */
    public Long getSizeOfZSet(String key) {
        return redisTemplate.opsForZSet().size(key);
    }
    
    /**
     * 获取某元素的score
     *
     * @param key   键值
     * @param value 键值对应值
     * @return score
     */
    public Double getScoreOfZSet(String key, String value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
    
    /**
     * 添加hash值
     *
     * @param key       键值
     * @param hashKey   键的hash
     * @param hashValue 键值对应值的hash
     */
    public void addElementOfHash(String key, String hashKey, String hashValue) {
        redisTemplate.opsForHash().put(key, hashKey, hashValue);
    }
    
    /**
     * 获取hash值
     *
     * @param key     键值
     * @param hashKey 键的hash
     * @return hash值
     */
    public Object getElementOfHash(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }
    
    /**
     * 添加所有值到hash
     *
     * @param key 键值
     * @param map 键值对应值的map集合
     */
    public void addAllElementOfHash(String key, Map<Object, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }
    
    /**
     * 增加hash值 <br>
     * 使用GenericToStringSerializer、StringRedisSerializer序列化器才能使用此方法 <br>
     * 否则报错 <br>
     * org.springframework.dao.InvalidDataAccessApiUsageException: ERR value is not an integer or out
     * of range; nested exception is redis.clients.jedis.exceptions.JedisDataException: ERR value is
     * not an integer or out of range <br>
     *
     * @param key     键值
     * @param hashKey 键的hash
     * @param count   增加hash值
     * @return 操作是否成功
     */
    public Long increment(String key, String hashKey, long count) {
        return redisTemplate.opsForHash().increment(key, hashKey, count);
    }
    
    /**
     * 获取所有hash值
     *
     * @param key      键值
     * @param hashKeys 键的hash
     * @return 所有hash值
     */
    public List<Object> getAllElementOfHash(String key, Collection<?> hashKeys) {
        return redisTemplate.opsForHash().multiGet(key, (Collection<Object>) hashKeys);
    }
    
    /**
     * 获取key对应的所有hash元素
     *
     * @param key 键值
     * @return 所有hash元素
     */
    public Map<Object, Object> getAllElementOfHash(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 获取所有hash的个数
     *
     * @param key 键值
     * @return hash的个数
     */
    public Long getSizeOfHash(String key) {
        return redisTemplate.opsForHash().size(key);
    }
    
    /**
     * 检查哈希键值是否存在
     *
     * @param key     键值
     * @param hashKey 键的hash
     * @return 是否存在
     */
    public Boolean isExisted(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }
    
    /**
     * 根据键值获取所有哈希键值
     *
     * @param key 键值
     * @return 所有哈希键值
     */
    public Set<Object> getHashKeysBy(String key) {
        return redisTemplate.opsForHash().keys(key);
    }
    
    /**
     * 根据键值获取所有哈希值
     *
     * @param key 键值
     * @return 所有哈希键值
     */
    public List<Object> getHashValuesBy(String key) {
        return redisTemplate.opsForHash().values(key);
    }
    
    /**
     * 根据键值获取哈希map
     *
     * @param key 键值
     * @return 哈希map
     */
    public Map<Object, Object> getHashMapBy(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 删除某键值中的哈希键值
     *
     * @param key     键值
     * @param hashKey 键的hash
     */
    public void deleteElementOfHash(String key, Object... hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }
    
    /**
     * 返回给定key的有效秒数，如果是-1则表示永远有效
     *
     * @param key 键值
     * @return 有效秒数
     */
    public Long getExpireSecondsBy(String key) {
        return redisTemplate.getExpire(key);
    }
    
    /**
     * 返回给定key的有效分钟数，如果是-1则表示永远有效
     *
     * @param key 键值
     * @return 有效分钟数
     */
    public Long getExpireMinutesBy(String key) {
        return redisTemplate.getExpire(key, TimeUnit.MINUTES);
    }
    
    /**
     * 返回给定key的有效小时数，如果是-1则表示永远有效
     *
     * @param key 键值
     * @return 有效小时数
     */
    public Long getExpireHoursBy(String key) {
        return redisTemplate.getExpire(key, TimeUnit.HOURS);
    }
    
    /**
     * 返回给定key的有效天数，如果是-1则表示永远有效
     *
     * @param key 键值
     * @return 有效天数
     */
    public Long getExpireDaysBy(String key) {
        return redisTemplate.getExpire(key, TimeUnit.DAYS);
    }
    
    /**
     * 添加元素到set
     *
     * @param key   键值
     * @param value 键值对应值
     */
    public void addElementOfSet(String key, String value) {
        redisTemplate.opsForSet().add(key, value);
    }
    
    /**
     * 从set获取所有元素
     *
     * @param key 键值
     * @return 所有元素
     */
    public Set<Object> getElementOfSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 判断元素是否是set的元素。若是则true，否则false
     *
     * @param key   键值
     * @param value 键值对应值
     * @return 是否是set的元素
     */
    public boolean isMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
    
    /**
     * 从set中移除某元素，成功返回1L，元素不存在或移除失败返回0L
     *
     * @param key   键值
     * @param value 键值对应值
     * @return 操作是否成功
     */
    public Long removeMemberOfSet(String key, Object... value) {
        return redisTemplate.opsForSet().remove(key, value);
    }
    
    /**
     * 获取set的元素个数
     *
     * @param key 键值
     * @return 元素个数
     */
    public Long getSizeOfSet(String key) {
        return redisTemplate.opsForSet().size(key);
    }
    
    /**
     * 从set中每次读取一条（出栈）
     *
     * @param key 键值
     * @return 出栈值
     */
    public Object getElementOfSetBy(String key) {
        return redisTemplate.opsForSet().pop(key);
    }
    
    /**
     * 根据redis的数据库index，获取key值 集群模式，index必定为0，否则报Cannot SELECT non zero index in cluster mode错误
     *
     * @return 数据库
     */
    public Long getDBSize() {
        return (Long)
                redisTemplate.execute(
                        (RedisCallback<Object>)
                                connection -> {
                                    connection.select(0);
                                    return connection.dbSize();
                                });
    }
}
